<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class MY_Output extends CI_Output
{
    function _get_cache_name()
    {
        $CFG = & load_class( 'Config' );
        $URI = & load_class( 'URI' );
        $RTR = & load_class( 'Router' );
        $request = $RTR->directory . $RTR->class . '/' . $RTR->method;
        $cache_list = $CFG->item( 'cache_list' );
        if (isset( $cache_list[$request] ) && is_array( $cache_list[$request] ))
        {
            if ($CFG->item( 'enable_query_strings' ) === false)
            {
                foreach ( $cache_list[$request] as $segment_index )
                {
                    $request .= '/' . $URI->segment( ( int ) $segment_index );
                }
            }
            else
            {
                foreach ( $cache_list[$request] as $key )
                {
                    $request .= '/' . isset( $_GET[$key] ) ? $_GET[$key] : '';
                }
            }
        }
        return $URI->logic_group . $request;
    }
    
    /**
     * Write Cache
     *
     * @param    string    $output    Output data to cache
     * @return    void
     */
    function _write_cache($output)
    {
        $CI = & get_instance();
        
        $cache_name = md5( $this->_get_cache_name() );
        
        $path = $CI->config->item( 'cache_path' );
        $cache_path = empty( $path ) ? APPPATH . 'cache/' : $path;
        
        if (! is_dir( $cache_path ) or ! is_really_writable( $cache_path ))
        {
            log_message( 'error', 'Unable to create directory: ' . $cache_path );
            return;
        }
        
        $cache_path .= substr( $cache_name, - 2 ) . DIRECTORY_SEPARATOR;
        
        if (! file_exists( $cache_path ))
        {
            if (! @mkdir( $cache_path, 0777, true ))
            {
                log_message( 'error', 'Create directory error' );
                return;
            }
        }
        
        if (! is_dir( $cache_path ) or ! is_really_writable( $cache_path ))
        {
            log_message( 'error', 'Unable to write cache file: ' . $cache_path );
            return;
        }

        $cache_path .= $cache_name;

        if ( ! $fp = @fopen($cache_path, 'w+b'))
        {
            log_message('error', 'Unable to write cache file: '.$cache_path);
            return;
        }

        if (flock($fp, LOCK_EX))
        {
            // If output compression is enabled, compress the cache
            // itself, so that we don't have to do that each time
            // we're serving it
            if ($this->_compress_output === TRUE)
            {
                $output = gzencode($output);

                if ($this->get_header('content-type') === NULL)
                {
                    $this->set_content_type($this->mime_type);
                }
            }

            $expire = time() + ($this->cache_expiration * 60);

            // Put together our serialized info.
            $cache_info = serialize(array(
                'expire'    => $expire,
                'headers'    => $this->headers
            ));

            $output = $cache_info.'ENDCI--->'.$output;

            for ($written = 0, $length = strlen($output); $written < $length; $written += $result)
            {
                if (($result = fwrite($fp, substr($output, $written))) === FALSE)
                {
                    break;
                }
            }

            flock($fp, LOCK_UN);
        }
        else
        {
            log_message('error', 'Unable to secure a file lock for file at: '.$cache_path);
            return;
        }

        fclose($fp);

        if (is_int($result))
        {
            chmod($cache_path, 0640);
            log_message('debug', 'Cache file written: '.$cache_path);

            // Send HTTP cache-control headers to browser to match file cache settings.
            $this->set_cache_header($_SERVER['REQUEST_TIME'], $expire);
        }
        else
        {
            @unlink($cache_path);
            log_message('error', 'Unable to write the complete cache content at: '.$cache_path);
        }
    }

    // --------------------------------------------------------------------

    /**
     * Update/serve cached output
     *
     * @uses    CI_Config
     * @uses    CI_URI
     *
     * @param    object    &$CFG    CI_Config class instance
     * @param    object    &$URI    CI_URI class instance
     * @return    bool    TRUE on success or FALSE on failure
     */
    function _display_cache(&$CFG, &$URI)
    {
        $cache_path = ($CFG->item('cache_path') === '') ? APPPATH.'cache/' : $CFG->item('cache_path');

        // Build the file path. The file name is an MD5 hash of the full URI
        $cache_name = md5( $this->_get_cache_name() );
        //echo $cache_name;return false;
        
        $filepath = $cache_path . substr( $cache_name, - 2 ) . '/' . $cache_name;

        if ( ! file_exists($filepath) OR ! $fp = @fopen($filepath, 'rb'))
        {
            return FALSE;
        }

        flock($fp, LOCK_SH);

        $cache = (filesize($filepath) > 0) ? fread($fp, filesize($filepath)) : '';

        flock($fp, LOCK_UN);
        fclose($fp);

        // Look for embedded serialized file info.
        if ( ! preg_match('/^(.*)ENDCI--->/', $cache, $match))
        {
            return FALSE;
        }

        $cache_info = unserialize($match[1]);
        $expire = $cache_info['expire'];

        $last_modified = filemtime($cache_path);

        // Has the file expired?
        if ($_SERVER['REQUEST_TIME'] >= $expire && is_really_writable($cache_path))
        {
            // If so we'll delete it.
            @unlink($filepath);
            log_message('debug', 'Cache file has expired. File deleted.');
            return FALSE;
        }
        else
        {
            // Or else send the HTTP cache control headers.
            $this->set_cache_header($last_modified, $expire);
        }

        // Add headers from cache file.
        foreach ($cache_info['headers'] as $header)
        {
            $this->set_header($header[0], $header[1]);
        }

        // Display the cache
        $this->_display(substr($cache, strlen($match[0])));
        log_message('debug', 'Cache file is current. Sending it to browser.');
        return TRUE;
    } 
}